﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Colorful.Control.Controls
{
    public class FXIcon : Image
    {        
        private class ChromaRecord
        {
            public readonly SolidColorBrush BlueChroma;
            public readonly SolidColorBrush GreenChroma;
            public readonly SolidColorBrush RedChroma;
            public readonly object Original;
            
            public ChromaRecord(object original, SolidColorBrush redChroma, SolidColorBrush greenChroma, SolidColorBrush blueChroma)
            {
                Original = original;
                RedChroma = redChroma;
                GreenChroma = greenChroma;
                BlueChroma = blueChroma;
            }

            public override bool Equals(object obj)
            {
                var record = obj as ChromaRecord;
                if (record != null && record.Original == Original && record.RedChroma == RedChroma && record.GreenChroma == GreenChroma)
                    return (record.BlueChroma == BlueChroma);

                return false;
            }

            public override int GetHashCode()
            {
                var hashCode = Original.GetHashCode();
                if (RedChroma != null)
                {
                    hashCode += RedChroma.Color.GetHashCode() * 2;
                }

                if (GreenChroma != null)
                {
                    hashCode += GreenChroma.Color.GetHashCode() * 3;
                }

                if (BlueChroma != null)
                {
                    hashCode += BlueChroma.Color.GetHashCode() * 4;
                }

                return hashCode;
            }
        }
       
        private static readonly Dictionary<ChromaRecord, object> ChromaCache;

        private bool sourceInvalid;
        private Visual rootVisual;
        private Point pixelSnappingOffset;

        public static readonly DependencyProperty BlueChromaProperty;
        public static readonly DependencyProperty DeselectedDrawingBrushProperty;
        public static readonly DependencyProperty DeselectedImageProperty;
        public static readonly DependencyProperty GreenChromaProperty;
        public static readonly DependencyProperty RedChromaProperty;
        public static readonly DependencyProperty SelectedDrawingBrushProperty;
        public static readonly DependencyProperty SelectedImageProperty;
        public static readonly DependencyProperty SourceBrushProperty;
        
        static FXIcon()
        {
            ChromaCache = new Dictionary<ChromaRecord, object>();
            SourceBrushProperty = DependencyProperty.Register("SourceBrush", typeof(DrawingBrush), typeof(FXIcon), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
            RedChromaProperty = DependencyProperty.RegisterAttached("RedChroma", typeof(SolidColorBrush), typeof(FXIcon), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
            GreenChromaProperty = DependencyProperty.RegisterAttached("GreenChroma", typeof(SolidColorBrush), typeof(FXIcon), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
            BlueChromaProperty = DependencyProperty.RegisterAttached("BlueChroma", typeof(SolidColorBrush), typeof(FXIcon), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
            SelectedImageProperty = DependencyProperty.RegisterAttached("SelectedImage", typeof(ImageSource), typeof(FXIcon), new PropertyMetadata(null));
            DeselectedImageProperty = DependencyProperty.RegisterAttached("DeselectedImage", typeof(ImageSource), typeof(FXIcon), new PropertyMetadata(null));
            SelectedDrawingBrushProperty = DependencyProperty.RegisterAttached("SelectedDrawingBrush", typeof(DrawingBrush), typeof(FXIcon), new PropertyMetadata(null));
            DeselectedDrawingBrushProperty = DependencyProperty.RegisterAttached("DeselectedDrawingBrush", typeof(DrawingBrush), typeof(FXIcon), new PropertyMetadata(null));
            StretchProperty.OverrideMetadata(typeof(FXIcon), new FrameworkPropertyMetadata(Stretch.None));
        }

        public FXIcon()
        {
            Loaded += Icon_Loaded;
            Unloaded += Icon_Unloaded;
        }
        
        private void Icon_Loaded(object sender, RoutedEventArgs e)
        {
            PresentationSource.AddSourceChangedHandler(this, SourceChanged);
            LayoutUpdated += Icon_LayoutUpdated;
            sourceInvalid = false;
        }

        private void Icon_Unloaded(object sender, RoutedEventArgs e)
        {
            PresentationSource.RemoveSourceChangedHandler(this, SourceChanged);
            LayoutUpdated -= Icon_LayoutUpdated;
            rootVisual = null;
        }

        private void Icon_LayoutUpdated(object sender, EventArgs e)
        {
            if (IsMeasureValid)
                UpdatePixelSnappingOffset();
        }

        private static bool IsClose(double x, double y)
        {
            return (x > (y * 0.9));
        }

        private Color ConvertColor(Color color)
        {
            if ((color.R != color.G) || (color.R != color.B))
            {
                if (color.G == color.B && RedChroma != null)
                {
                    return ScaleColor(RedChroma.Color, color.R, color.G, color.A);
                }
                if (color.R == color.B && GreenChroma != null)
                {
                    return ScaleColor(GreenChroma.Color, color.G, color.R, color.A);
                }
                if (color.R == color.G && BlueChroma != null)
                {
                    return ScaleColor(BlueChroma.Color, color.B, color.R, color.A);
                }
            }
            return color;
        }


        public static DrawingBrush GetSelectedDrawingBrush(DependencyObject obj)
        {
            return (DrawingBrush)obj.GetValue(SelectedDrawingBrushProperty);
        }

        public static ImageSource GetSelectedImage(DependencyObject obj)
        {
            return (ImageSource)obj.GetValue(SelectedImageProperty);
        }

        public static DrawingBrush GetDeselectedDrawingBrush(DependencyObject obj)
        {
            return (DrawingBrush)obj.GetValue(DeselectedDrawingBrushProperty);
        }

        public static ImageSource GetDeselectedImage(DependencyObject obj)
        {
            return (ImageSource)obj.GetValue(DeselectedImageProperty);
        }

        private void SourceChanged(object o, SourceChangedEventArgs args)
        {
            sourceInvalid = true;
        }

        public static Point GetPixelSnappingOffset(Visual visual)
        {
            var source = PresentationSource.FromVisual(visual);
            return source != null ? GetPixelSnappingOffset(visual, source.RootVisual) : new Point();
        }

        private void UpdatePixelSnappingOffset()
        {
            if (sourceInvalid) 
                return;

            if (rootVisual == null)
            {
                var source = PresentationSource.FromVisual(this);
                if (source == null)
                {
                    sourceInvalid = true;
                    return;
                }

                rootVisual = source.RootVisual;
            }

            var offset = GetPixelSnappingOffset(this, rootVisual);
            PixelSnappingOffset = offset;
        }

        private static Point GetPixelSnappingOffset(Visual visual, Visual rootVisual)
        {
            var point = new Point();
            if (rootVisual != null)
            {
                var transform = visual.TransformToAncestor(rootVisual) as Transform;
                if (transform != null && transform.Value.HasInverse)
                {
                    point = visual.PointFromScreen(visual.PointToScreen(point));
                }
            }
            return point;
        }

        private static Color ScaleColor(Color color, byte primary, byte white, byte alpha)
        {
            return Color.FromArgb((byte)((alpha * color.A) / 255), (byte)(((((double)color.R) / 255) * (primary - white)) + white), (byte)(((((double)color.G) / 255) * (primary - white)) + white), (byte)(((((double)color.B) / 255) * (primary - white)) + white));
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            var location = PixelSnappingOffset;
            var source = Source as BitmapSource;
            if ((SourceBrush == null) || (source != null && IsClose(source.Width, RenderSize.Width) && IsClose(source.Height, RenderSize.Height)))
            {
                var imageSource = RenderSource;
                if (imageSource != null)
                    drawingContext.DrawImage(imageSource, new Rect(location, RenderSize));
            }
            else
            {
                drawingContext.DrawRectangle(RenderSourceBrush, null, new Rect(location, RenderSize));
            }
        }

        public SolidColorBrush BlueChroma
        {
            get { return (SolidColorBrush)GetValue(BlueChromaProperty); }
            set { SetValue(BlueChromaProperty, value); }
        }

        public SolidColorBrush GreenChroma
        {
            get { return (SolidColorBrush)GetValue(GreenChromaProperty); }
            set { SetValue(GreenChromaProperty, value); }
        }

        private Point PixelSnappingOffset
        {
            get { return pixelSnappingOffset; }
            set
            {
                if (pixelSnappingOffset != value)
                {
                    pixelSnappingOffset = value;
                    LayoutUpdated -= Icon_LayoutUpdated;
                    InvalidateVisual();
                }
            }
        }

        public SolidColorBrush RedChroma
        {
            get { return (SolidColorBrush)GetValue(RedChromaProperty); }
            set { SetValue(RedChromaProperty, value); }
        }

        private ImageSource RenderSource
        {
            get
            {
                object o;
                if (Source == null)
                    return null;

                if (RedChroma == null && GreenChroma == null && BlueChroma == null)
                    return Source;

                var key = new ChromaRecord(Source, RedChroma, GreenChroma, BlueChroma);
                if (ChromaCache.TryGetValue(key, out o))
                    return (ImageSource)o;

                var source = ColorSwapper.SwapColors(Source, ConvertColor);
                ChromaCache.Add(key, source);
                return source;
            }
        }

        private DrawingBrush RenderSourceBrush
        {
            get
            {
                object o;
                if (SourceBrush == null)
                    return null;

                if (RedChroma == null && GreenChroma == null && BlueChroma == null)
                {
                    return SourceBrush;
                }
                var key = new ChromaRecord(SourceBrush, RedChroma, GreenChroma, BlueChroma);
                if (ChromaCache.TryGetValue(key, out o))
                {
                    return (DrawingBrush)o;
                }
                var brush = (DrawingBrush)ColorSwapper.SwapColors(SourceBrush, ConvertColor);
                ChromaCache.Add(key, brush);
                return brush;
            }
        }

        public DrawingBrush SourceBrush
        {
            get { return (DrawingBrush)GetValue(SourceBrushProperty); }
            set { SetValue(SourceBrushProperty, value); }
        }
    }
}
